Linux性能优化之进程状态及案例分析 您所在的位置:网站首页 linux login进程 Linux性能优化之进程状态及案例分析

Linux性能优化之进程状态及案例分析

2023-04-15 01:59| 来源: 网络整理| 查看: 265

文章对 Linux 系统下进程的几种状态进行介绍,并对系统出现大量僵尸进程和不可中断进程的场景进行分析,使用常用的几种工具进行问题分析定位。

top 和 ps 是最常用的查看进程状态的工具,下面是 top 命令输出的示例,S 列(也就是 Status 列)表示进程的状态。

任务: 340 total, 2 running, 246 sleeping, 0 stopped, 12 zombie %Cpu(s): 1.1 us, 22.8 sy, 0.0 ni, 54.2 id, 15.5 wa, 0.0 hi, 6.4 si, 0.0 st KiB Mem : 3994736 total, 846960 free, 1498772 used, 1649004 buff/cache KiB Swap: 2097148 total, 2092272 free, 4876 used. 2206556 avail Mem 进程 USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 148 root 0 -20 0 0 0 I 21.2 0.0 5:46.66 kworker/2:1H-kb 151 root 0 -20 0 0 0 R 17.2 0.0 5:44.51 kworker/1:1H+kb 16659 root 20 0 0 0 0 Z 11.3 0.0 0:01.25 app 16660 root 20 0 0 0 0 Z 11.3 0.0 0:01.20 app 150 root 0 -20 0 0 0 I 9.3 0.0 4:41.60 kworker/3:1H-ev 16662 root 20 0 70056 65776 264 D 8.6 1.6 0:00.26 app 16663 root 20 0 70056 65776 264 D 7.6 1.6 0:00.23 app 802 root 0 -20 230352 7676 6472 S 2.6 0.2 7:35.09 vmtoolsd

上面数据的 S 列可以看到 R、D、S、I 、Z 几个状态,下面对进程的这几种状态进行介绍。

R 状态:R 是 Running 或 Runnable 的缩写,表示进程在 CPU 的就绪队列中,正在运行或者正在等待运行。D 状态:D 是 Disk Sleep 的缩写,也就是不可中断状态睡眠(Uninterruptible Sleep),一般表示进程正在跟硬件交互,并且交互过程不允许被其他进程或中断打断。Z 状态:Z 是 Zombie 的缩写,它表示僵尸进程,也就是进程实际上已经结束了,但是父进程还没有回收它的资源(比如进程的描述符、PID 等)。S 状态:S 是 Interruptible Sleep 的缩写,也就是可中断状态睡眠,表示进程因为等待某个事件而被系统挂起。当进程等待的事件发生时,它会被唤醒并进入 R 状态。I 状态:I 是 Idle 的缩写,也就是空闲状态,用在不可中断睡眠的内核线程上。硬件交互导致的不可中断进程用 D 表示,但对某些内核线程来说,它们有可能实际上并没有任何负载,用 Idle 正是为了区分这种情况。要注意,D 状态的进程会导致平均负载升高, I 状态的进程却不会。

进程的状态除了上面的状态之外,还有 T 和 t 状态,这两种状态都表示进程处于停止状态,但使得进程停止的原因有所差异,如下所示。

T 状态:由信号触发的停止状态,比如向一个进程发送 SIGSTOP 信号,它就会因响应这个信号变成暂停状态(Stopped);再向它发送 SIGCONT 信号,进程又会恢复运行(如果进程是终端里直接启动的,则需要你用 fg 命令,恢复到前台运行)。t 状态:由调试跟踪触发的停止状态,当使用调试器(如 gdb)调试一个进程时,在使用断点中断进程后,进程就会变成跟踪状态,这其实也是一种特殊的暂停状态,只不过可以用调试器来跟踪并按需要控制进程的运行。

本案例模拟多进程对磁盘进行读取,使用相关的性能分析工具对例程出现的性能问题进行分析,案例源码在文章后面,编译出的可执行程序名称为 app。

2.1 异常信息分析

在 Ubuntu 系统中打开一个终端,执行 app 进程,打开第二个终端,使用 top 命令查看进程运行状态,如下所示。

对上面图片中的信息进行分析,找出系统可能存在的异常情况。

第一行中过去 15 分钟、5 分钟、1 分钟的平均负载依次升高,说明平均负载正在升高,过去 1 分钟的平均负载已经快接近 CPU 的个数,说明系统可能已经出现了性能瓶颈。第二行中存在较多的僵尸进程,说明子进程退出时没有被回收。查看 CPU 信息,用户 CPU 使用率较低,系统 CPU 使用率和 iowait 有所上升。查看每个进程的信息,当前运行 app 进程的 CPU 使用率为 20% 左右,且这两个进程的状态为 D 状态,说明该进程有可能正在等待 I/O。

对以上分析进行总结,得出以下两点结论。

系统中僵尸进程在不断增加,说明程序没有正确回收子进程的资源。系统平均负载在升高,CPU 使用率和 iowait 有所上升,说明平均负载的升高可能是 iowait 引起的,iowait 与系统调用有关。

2.2 iowait 分析

根据上面得出的结论,系统平均负载的上升可能是由于 iowait 引起的,这里推荐一个查看系统 I/O 情况的工具,dstat 可以同时查看 CPU 和 I/O 这两种资源的使用情况,便于对比分析,以 1 秒为间隔,连续输出10组数据,如下所示。

从 dstat 的输出可以看到,当 iowait(wai)高时,磁盘的读请求(read)都会很大。这说明 iowait 的升高跟磁盘的读请求有关,很可能就是磁盘读导致的。

接下来,需要找出读取磁盘的进程,上面 top 的输出中,进程 id 为 6758 和 6759 的进程处于 D 状态,使用 pidstat 查看这两个进程的情况,-d 参数可以显示 I/O 的使用情况,如下所示。

上图进程 ID 为 6758 和 6759 的进程 kB_rd/s 数据值为 0,说明不是这两个进程在读取磁盘数据,继续使用 pidstat 查看所有进程的 I/O 使用情况,如下所示(以 1 秒为间隔,输出 3 组数据)。

上图中 app 进程的 kB_rd/s 的值较大,最大达到 211MB/s,说明 iowait 升高就是 app 进程在读取数据导致的。进程分为用户态和内核态,进程想要访问磁盘,就必须使用系统调用,这也是通过 top 查看时,系统 CPU 和 iowait 一起升高的原因,接下来的重点就是找出 app 进程的系统调用。

strace 工具可以查看进程的系统调用栈,指定 pid 查看进程的系统调用栈,如下所示。

上图操作显示无权限,但是当前终端权限已经为 root,检查一下进程的状态是否正常,如下所示。

根据图片信息可以看到,当前进程 ID 为 6945 的 app 进程状态为僵尸状态,僵尸进程是已经退出的进程,所以就没法儿继续分析它的系统调用。

之前提到的 perf 工具,也可以查看进程的调用关系,使用"perf record -g"命令记录调用关系,等待 10 秒左右,使用"perf report"命令报告调用关系,展开 app 进程的调用关系,如下所示。

app 通过系统调用 sys_read() 读取数据,从 new_sync_read 和 blkdev_direct_IO 可以看出,进程正在对磁盘进行直接读,也就是绕过了系统缓存,每个读请求都会从磁盘直接读,这其实就是导致 iowait 升高的原因,查看 app.c 中磁盘的打开方式,如下所示。

open(disk, O_RDONLY | O_DIRECT | O_LARGEFILE, 0755);

直接读写磁盘,是直接控制磁盘的读写,不借助系统缓存的优化进行操作,这会导致读写性能受到 I/O 瓶颈的限制,一般直接读写只应用于对 I/O 敏感型的场景,比如数据库系统。但在大部分情况下,最好还是通过系统缓存来优化磁盘 I/O,在例程中删除 O_DIRECT 这个选项。

修改后启动 app 进程,然后使用 top 工具查看系统信息,如下所示,当前平均负载、CPU 使用率和 iowait 已经非常低了,说明刚才的改动已经成功修复了 iowait 高导致系统平均负载升高的问题。

2.3 僵尸进程问题分析

僵尸进程是因为父进程没有回收子进程的资源造成的,因此,要解决僵尸进程的问题,就要找出父进程,然后在父进程里等待回收子进程,可以通过 pstree 找出子进程对应的父进程,如下所示(-a 表示显示命令行选项,p表PID,s表示指定进程的父进程)。

通过上面图片看到,进程 id 为 3574 进程的父进程是 3571,还是 app 进程,接下来查看 app 应用程序的代码,分析子进程结束的处理是否正确,比如有没有调用 wait() 或 waitpid() ,抑或是,有没有注册 SIGCHLD 信号的处理函数。app 应用程序对子进程的创建和清理代码如下。

这段代码虽然看起来调用了 wait() 函数等待子进程结束,但却错误地把 wait() 放到了 for 死循环的外面,wait() 函数实际上并没被调用到,把它挪到 for 循环的里面就可以了。修改后,可以使用 top 命令检查一下,发现系统的僵尸进程数量一直为 0。

2.4 例程源码

#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #define DISK_PATH "/dev/sda1" #define BUF_SIZE 64 * 1024 * 1024 #define BUF_COUNT 30 void sub_process(const char *disk, size_t buffer_size, size_t count) { int fd = open(disk, O_RDONLY | O_DIRECT | O_LARGEFILE, 0755); if (fd < 0) { perror("failed to open disk"); _exit(1); } unsigned char *buf; posix_memalign((void **)&buf, 512, buffer_size); size_t read_bytes = 0; while (read_bytes < count * buffer_size) { size_t ret = read(fd, buf, buffer_size); if (ret < 0) { perror("failed to read contents"); close(fd); free(buf); _exit(1); } read_bytes += ret; } close(fd); free(buf); _exit(0); } int main(int argc, char **argv) { int status = 0; long buffer_size = BUF_SIZE; long buffer_count = BUF_COUNT; char *disk = DISK_PATH; printf("Reading data from disk %s with buffer size %ld and count %ld\n", disk, buffer_size, buffer_count); for (;;) { for (int i = 0; i < 2; i++) { if (fork() == 0) { sub_process(disk, buffer_size, buffer_count); } } sleep(5); } while (wait(&status) > 0); return 0; }

原文:Linux性能优化之进程状态及案例分析

侵删~

相关视频推荐

LinuxC++下多进程、多线程、线程使用场景分析

红黑树在linux内核中的3种场景(红黑树证明,进程管理CFS,内存管理)

90分钟搞定协程、线程、进程大厂面试丨线程池如何做到最高效

LinuxC++后台服务器开发架构师免费学习地址

【文章福利】:小编整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!~点击832218493加入(需要自取)



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有